from typing import Any, Optional

from scrapy.crawler import Crawler
from crawler.base_spider import BaseSpider
import scrapy
from scrapy_splash import SplashRequest
from PIL import Image
from io import BytesIO
import random
from urllib.parse import urlencode, urlparse, parse_qs
from constants import Ibdi
from bs4 import BeautifulSoup
import requests
import os
from setting import ProjectSetting
from scrapy.http import Request

class IbdiSpider(BaseSpider):
    name = Ibdi.NAME
    device_print: Optional[str] = None
    captcha_code: Optional[str] = None

    def __init__(self, account):
        super().__init__()
        self.captcha_image_path = os.path.join(self.data_dir, "captcha.png")
        self.account = account
        self._session = requests.session()

    def start_requests(self):
        assert self.account is not None, "Please init the account info first!"
        yield Request(
            url=Ibdi.BASE_URL+"AuthenticationController",
            method="POST",
            body=urlencode(Ibdi.PARAMS),
            callback=self.parse)

    def parse(self, response):
        self.logger.info("Received initial page: %s", response.url)
        pm_fp_js_path = os.path.join(ProjectSetting.EXTENTIONS_DIR, self.name, "pm_fp.js")
        js_executor = self.js_executor(pm_fp_js_path)
        self.device_print = js_executor.execute_function("encode_deviceprint")
        self.logger.info("Get device print: {}".format(self.device_print))

        soup = BeautifulSoup(response.text, 'html.parser')
        captcha_image_node = soup.find('img', {'id': 'IMAGECAPTCHA'})

        form_data = {}
        form = soup.find('form', {'name': 'AuthenticationFG'})

        for input_element in form.find_all('input'):
            input_name = input_element.get('name')
            input_value = input_element.get('value')
            form_data[input_name] = input_value

        continue_login_action_value = form.get("action")

        if captcha_image_node:
            image_src = captcha_image_node.get('src')
            parsed_url = urlparse(image_src)
            # query_parameters = parse_qs(parsed_url.query)
            # bwayparam = query_parameters["bwayparam"][0]
            self.logger.info("Get image source: {}".format(image_src))
        else:
            self.logger.error("Captcha image not found in HTML response.")

        assert image_src != "" and continue_login_action_value != "", "Fail to find captcha image and continue login value"
        image_url = Ibdi.BASE_URL + image_src
        continue_login_url = Ibdi.BASE_URL + continue_login_action_value

        data = {"t": random.random()}

        body = urlencode(data)
        yield Request(
            url=image_url,
            method="GET",
            headers=Ibdi.HEADERS,
            body=body,
            callback=self.parse_captcha,
            meta={"continue_login_url": continue_login_url})

    def parse_captcha(self, response):
        self.save_captcha_image(response, self.captcha_image_path)
        captcha_code = self.recognize_image_code(self.captcha_image_path, "10110")
        continue_login_url = response.meta.get("continue_login_url")

        data = {
            "AuthenticationFG.USER_PRINCIPAL": self.account.name,
            "AuthenticationFG.VERIFICATION_CODE": captcha_code,
        }

        body = urlencode(data)
        yield Request(
            url=continue_login_url,
            method="POST",
            body=body,
            callback=self.parse_after_continue_login)

    def encrypt_password(self, pub_key):
        encrypt_js_path = os.path.join(ProjectSetting.EXTENTIONS_DIR, self.name, "encrypt.js")
        js_executor = self.js_executor()
        encrypted_password = js_executor(encrypt_js_path, self.account.password, pub_key)
        self.logger.info("Get encrypted password: {}".format(encrypted_password))
        return encrypted_password

    def parse_after_continue_login(self, response):
        soup = BeautifulSoup(response.text, 'html.parser')
        form = soup.find('form', {'name': 'AuthenticationFG'})
        login_action_value = form.get("action")
        login_url = Ibdi.BASE_URL + login_action_value
        pub_key = ""
        for input_element in form.find_all('input'):
            if input_element.get('name') == "__JS_ENCRYPT_KEY__":
                pub_key = input_element.get('value')
                break
        self.logger.info("Get pub key: {}".format(pub_key))
        encrypted_password = self.encrypt_password(self.account.password, pub_key)
        form_data = {}
        for input_element in form.find_all('input'):
            input_name = input_element.get('name')
            input_value = input_element.get('value')
            if input_name in {"pm_fp", "deviceprint", "AuthenticationFG.DEVICE_PRINT", "TranRequestManagerFG.DEVICE_PRINT"}:
                form_data[input_value] = self.device_print
            elif input_name == "AuthenticationFG.ACCESS_CODE":
                form_data[input_value] = encrypted_password
            elif input_name in {"JS_ENABLED_FLAG", "DECRYPT_FLAG"}:
                form_data[input_value] = "Y"
            elif input_name == None or input_name == "action.BACK":
                continue
            elif input_value == None:
                form_data[input_value] = ""
            suffix += "&{}={}".format(input_name, input_value)

        form_data["RIA_TARGETS"] = ["STATUS_MOBILELOGIN,,,"]
        yield SplashRequest(
            url=login_url,
            callback=self.action_after_login,
            endpoint='execute',
            args={'wait': 5, 'lua_source': self.get_lua_script(form_data, len(encrypted_password))})

    def get_lua_script(self, form_data, maxlength):
        lua_script = f"""
        function main(splash)
            splash:go(splash.args.url)
            splash:wait(2)
            local form_data = {form_data}
            local url = splash.args.url
            for key, value in pairs(form_data) do
                local input_element = splash:evaljs(string.format("document.querySelector('input[name=%s]')", key))
                if input_element and input_element.type == 'password' then
                    input_element.maxLength = {maxlength}
                end
                splash:runjs(string.format("document.querySelector('input[name=%s]').value = %s", key, value))
            end

            splash:wait(2)
            splash:runjs("document.querySelector('form').submit()")

            splash:wait(5)
            return splash:html()
        end
        """

    def action_after_login(self, response):
        pass
